import {EventEmitter} from 'events';
import {Point} from '../type';
import {BASE_HELP_CONTROL_POINT_ID, BASE_HELP_HIDDEN} from '../constants';
import {create, createHidden, getElement, off, on, remove} from '../utils/DomUtil';
import {formatPlotData} from '../utils/FormatUtil';
import {PlotTypes} from '../enum/PlotTypes';

export default class PlotEdit extends EventEmitter {
    private activePlot: any;
    private startPoint: any;
    private ghostControlPoints: any;
    // 控制点列表
    private controlPoints: Array<any>;
    private mouseOver: boolean;
    private elementTable: any;
    private activeControlPointId: any;
    private mapDragPan: any;
    private previousCursor: any;

    private readonly _map: any;
    private readonly _mapViewport: any;
    private readonly _options: any;

    constructor(map: any, options: any = {}) {
        super();
        this._map = map;
        this._mapViewport = map.getViewport();
        options.limitControlPoints = options.limitControlPoints ? options.limitControlPoints : 50;
        this._options = options || {};

        this.activePlot = null;
        this.startPoint = null;
        this.ghostControlPoints = null;
        this.controlPoints = [];
        this.mouseOver = false;
        this.elementTable = {};
        this.activeControlPointId = null;
        this.mapDragPan = null;
        this.previousCursor = null;
    }

    /**
     * 激活编辑工具
     * @param plot
     */
    public activate = (plot: any): void => {
        if (plot && plot.get('isPlot') && plot.getGeometry().isPlot && plot !== this.activePlot) {
            this.deactivate();
            this.activePlot = plot;
            this.previousCursor = this._map.getTargetElement().style.cursor;
            window.setTimeout(() => {
                this.emit('active_plot_change', this.activePlot);
            }, 500);
            this._map.on('pointermove', this.plotMouseOverOutHandler);
            this.initHelperDom();
            this.initControlPoints();
        }
    };

    /**
     * 注销编辑工具
     */
    public deactivate = (): void => {
        this.activePlot = null;
        this.mouseOver = false;
        this._map.getTargetElement().style.cursor = this.previousCursor;
        this.previousCursor = null;
        this.destroyHelperDom();
        this.disconnectEventHandlers();
        this.enableMapDragPan();
        this.elementTable = {};
        this.activeControlPointId = null;
        this.startPoint = null;
    };

    /**
     * 初始化提示DOM
     */
    private initHelperDom = (): void => {
        if (!this._map || !this.activePlot) return;
        let parent: any = this.getMapParentElement();
        if (!parent) return;
        let hiddenDiv: HTMLElement = createHidden('div', parent, BASE_HELP_HIDDEN);
        let cPnts: Array<any> = this.getControlPoints();
        if (cPnts && Array.isArray(cPnts) && cPnts.length > 0) {
            cPnts.forEach((item: any, index: number): void => {
                let id: string = `${BASE_HELP_CONTROL_POINT_ID}-${index}`;
                create('div', BASE_HELP_CONTROL_POINT_ID, hiddenDiv, id);
                this.elementTable[id] = index;
            });
        }
    };

    /**
     * 销毁帮助提示DOM
     */
    private destroyHelperDom = (): void => {
        if (this.controlPoints && Array.isArray(this.controlPoints) && this.controlPoints.length > 0) {
            this.controlPoints.forEach((item: any, index: number): void => {
                // @ts-ignore
                if (item && item instanceof ol.Overlay) {
                    this._map.removeOverlay(item);
                }
                let element: HTMLElement = getElement(`${BASE_HELP_CONTROL_POINT_ID}-${index}`);
                if (element) {
                    if (off) {
                        off(element, 'mousedown', this.controlPointMouseDownHandler);
                        off(element, 'mousemove', this.controlPointMouseMoveHandler2);
                    }
                }
            });
            this.controlPoints = [];
        }
        let parent: any = this.getMapParentElement();
        let hiddenDiv: any = getElement(BASE_HELP_HIDDEN);
        if (hiddenDiv && parent) {
            remove(hiddenDiv);
        }
    };

    /**
     * 初始化要素控制点
     */
    private initControlPoints = (): void => {
        this.controlPoints = [];
        let cPnts: Array<any> = this.getControlPoints();
        if (cPnts && Array.isArray(cPnts) && cPnts.length > 0) {
            if (cPnts.length > this._options['limitControlPoints'] && this._options.hasOwnProperty('limitControlPoints') && this._options['limitControlPoints'] > 2) {
                const n: number = Math.floor(cPnts.length / (this._options['limitControlPoints'] - 2)) || 1;
                for (let i: number = 0; i < this._options['limitControlPoints'] - 2; i++) {
                    const index: number = (i + 1) * n - 1;
                    this.addControlPoint(cPnts, index);
                }
                this.addControlPoint(cPnts, 0);
                this.addControlPoint(cPnts, cPnts.length - 1);
            } else {
                for (let i: number = 0; i < cPnts.length; i++) {
                    this.addControlPoint(cPnts, i);
                }
            }
            this._map.render();
        }
    };

    /**
     * 添加控制点
     * @param points
     * @param index
     */
    private addControlPoint = (points: Array<any>, index: number): void => {
        const id: string = `${BASE_HELP_CONTROL_POINT_ID}-${index}`;
        this.elementTable[id] = index;
        const element: HTMLElement = getElement(id);
        // @ts-ignore
        const pnt: any = new ol.Overlay({id: id, position: points[index], positioning: 'center-center', element});
        this.controlPoints.push(pnt);
        this._map.addOverlay(pnt);
        if (on) {
            on(element, 'mousedown', this.controlPointMouseDownHandler);
            on(element, 'mousemove', this.controlPointMouseMoveHandler2);
        }
    };

    private controlPointMouseDownHandler = (evt: any): void => {
        this.activeControlPointId = evt.target.id;
        this._map.on('pointermove', this.controlPointMouseMoveHandler);
        if (on) {
            on(this._mapViewport, 'mouseup', this.controlPointMouseUpHandler);
        }
    };

    private controlPointMouseMoveHandler2 = (evt: any): void => {
        evt.stopImmediatePropagation();
    };

    private controlPointMouseMoveHandler = (event: any): void => {
        let coordinate: any = event.coordinate;
        if (this.activeControlPointId) {
            let plot: any = this.activePlot.getGeometry();
            let index: number = this.elementTable[this.activeControlPointId];
            plot.updatePoint(coordinate, index);
            let overlay: any = this._map.getOverlayById(this.activeControlPointId);
            if (overlay) {
                overlay.setPosition(coordinate);
            }
        }
    };

    private controlPointMouseUpHandler = (event: any): void => {
        this._map.un('pointermove', this.controlPointMouseMoveHandler);
        if (off) {
            off(this._mapViewport, 'mouseup', this.controlPointMouseUpHandler);
            const plotType: string = this.activePlot.getProperties().plotType;
            if (plotType === PlotTypes.POINT) {
                this.emit('moveEnd', {
                    type: 'moveEnd',
                    originalEvent: event,
                    feature: this.activePlot,
                    data: formatPlotData(this.activePlot.getProperties().plotType, this.activePlot)
                });
            } else {
                this.emit('deformationEnd', {
                    type: 'deformationEnd',
                    originalEvent: event,
                    feature: this.activePlot,
                    data: formatPlotData(this.activePlot.getProperties().plotType, this.activePlot)
                });
            }
        }
    };

    /**
     * 获取地图元素的父元素
     */
    private getMapParentElement = (): any => {
        let mapElement: HTMLElement = this._map.getTargetElement() as HTMLElement;
        if (!mapElement) return;
        return mapElement.parentNode;
    };

    private getControlPoints = (): any => {
        let points: Array<Point> = [];
        if (this.activePlot) {
            let geom: any = this.activePlot.getGeometry();
            if (geom) {
                points = geom.getPoints();
            }
        }
        return points;
    };

    private plotMouseOverOutHandler = (evt: any): void => {
        const feature: any = this._map.forEachFeatureAtPixel(evt.pixel, (feature: any): any => feature);
        if (feature && feature === this.activePlot) {
            if (!this.mouseOver) {
                this.mouseOver = true;
                this._map.getTargetElement().style.cursor = 'move';
                this._map.on('pointerdown', this.plotMouseDownHandler);
            }
        } else {
            if (this.mouseOver) {
                this.mouseOver = false;
                this._map.getTargetElement().style.cursor = 'default';
                this._map.un('pointerdown', this.plotMouseDownHandler);
            }
        }
    };

    private plotMouseDownHandler = (evt: any): void => {
        this.ghostControlPoints = this.getControlPoints();
        this.startPoint = evt.coordinate;
        this.disableMapDragPan();
        this._map.on('pointerup', this.plotMouseUpHandler);
        this._map.on('pointerdrag', this.plotMouseMoveHandler);
    };

    private plotMouseMoveHandler = (event: any): void => {
        let deltaX: number = event.coordinate[0] - this.startPoint[0];
        let deltaY: number = event.coordinate[1] - this.startPoint[1];
        let newPoints: any = [];
        if (this.ghostControlPoints && Array.isArray(this.ghostControlPoints) && this.ghostControlPoints.length > 0) {
            for (let i: number = 0; i < this.ghostControlPoints.length; i++) {
                let coordinate: any = [this.ghostControlPoints[i][0] + deltaX, this.ghostControlPoints[i][1] + deltaY];
                newPoints.push(coordinate);
                let id: string = BASE_HELP_CONTROL_POINT_ID + '-' + i;
                let overlay: any = this._map.getOverlayById(id);
                if (overlay) {
                    overlay.setPosition(coordinate);
                    overlay.setPositioning('center-center');
                }
            }
        }
        const geometry: any = this.activePlot.getGeometry();
        geometry.setPoints(newPoints);
    };

    private plotMouseUpHandler = (event: any): void => {
        this.enableMapDragPan();
        this._map.un('pointerup', this.plotMouseUpHandler);
        this._map.un('pointerdrag', this.plotMouseMoveHandler);
        this.emit('moveEnd', {
            type: 'moveEnd',
            originalEvent: event,
            feature: this.activePlot,
            data: formatPlotData(this.activePlot.getProperties().plotType, this.activePlot)
        });
    };

    private disconnectEventHandlers = (): void => {
        this._map.un('pointermove', this.plotMouseOverOutHandler);
        this._map.un('pointermove', this.controlPointMouseMoveHandler);
        if (off) {
            off(this._mapViewport, 'mouseup', this.controlPointMouseUpHandler);
        }
        this._map.un('pointerdown', this.plotMouseDownHandler);
        this._map.un('pointerup', this.plotMouseUpHandler);
        this._map.un('pointerdrag', this.plotMouseMoveHandler);
    };

    private disableMapDragPan = (): void => {
        let interactions: Array<any> = this._map.getInteractions().getArray();
        interactions.every((item: any): boolean => {
            // @ts-ignore
            if (item instanceof ol.interaction.DragPan) {
                this.mapDragPan = item;
                this._map.removeInteraction(item);
                return false;
            } else {
                return true;
            }
        });
    };

    private enableMapDragPan = (): void => {
        // @ts-ignore
        if (this.mapDragPan && this.mapDragPan instanceof ol.interaction.DragPan) {
            this._map.addInteraction(this.mapDragPan);
            this.mapDragPan = null;
        }
    };
}